#include "log.h"
#include <string.h>
#include <strings.h>

#ifndef LOG_COLOR
#define LOG_COLOR 1
#endif

static const char *log_level_strs[] = {"DEBUG", "INFO", "WARNING", "ERROR", "FATAL"};
#if LOG_COLOR
static const char *log_level_colors[] = {"\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m"};
#endif

typedef struct log_event_st
{
    va_list args;
    const char *fmt;
    const char *file;
    struct tm *time;
    char tag[LOG_TAG_LEN];
    FILE *fp;
    int line;
    int level;
} log_event_t;
typedef void (*log_cb_impl)(log_event_t *ev);

typedef struct log_callback_st
{
    log_cb_impl cb_func;
    FILE *fp;
    int lowlevel;
    char tag[LOG_TAG_LEN];
} log_callback_t;

typedef struct log_st
{
    log_lock_impl lock_func;
    log_callback_t cb_func_arr[LOG_CB_FUNC_ARR_LEN];
} log_t;

static log_t LOG = {0};

/// @brief 获取日志等级字符串
/// @param level 日志等级
/// @return
const char *log_level_str(int level)
{
    return log_level_strs[level];
}
/// @brief 获取对应字符串的日志等级
/// @param str
/// @return
int log_str_level(const char *str)
{
    int level = 0;
    if (str == NULL)
        return level;
    for (size_t i = 0; i < sizeof(log_level_strs) / sizeof(char *); i++)
    {
        if (strncasecmp(log_level_strs[i], str, strlen(log_level_strs[i])) == 0)
        {
            level = i;
            break;
        }
    }
    return level;
}

/// @brief 设置写入文件日志操作时上锁解锁函数
/// @param func 上锁解锁函数
void log_set_lock(log_lock_impl func)
{
    LOG.lock_func = func;
}

/// @brief 标准输出回调函数
/// @param event 用户数据结构体变量 地址
static void stdout_cb(log_event_t *event)
{
    char buf[16];
    buf[strftime(buf, sizeof(buf), "%H:%M:%S", event->time)] = '\0';
#if LOG_COLOR
    fprintf(
        event->fp, "%s\t%s%-7s\t\x1b[0m \x1b[90m%s:%d:\x1b[0m ",
        buf, log_level_colors[event->level], log_level_strs[event->level],
        event->file, event->line);
#else
    fprintf(
        event->fp, "%s\t%-7s\t%s:%d: ",
        buf, log_level_strs[event->level], event->file, event->line);
#endif
    vfprintf(event->fp, event->fmt, event->args);
    fprintf(event->fp, "\n");
    fflush(event->fp);
}

/// @brief 写入文件回调函数
/// @param event 用户数据结构体变量 地址
static void fp_cb(log_event_t *event)
{
    char buf[64];
    buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", event->time)] = '\0';
    fprintf(
        event->fp, "%s\t%-7s\t[%s]\t%s:%d: ",
        buf, log_level_strs[event->level], event->tag, event->file, event->line);
    vfprintf(event->fp, event->fmt, event->args);
    fprintf(event->fp, "\n");
    fflush(event->fp);
}

/// @brief 添加文件描述符指针到全局中,并且命名指定tag字符串
/// @param fp 文件描述符指针 建议使用 ab+ 或 w 打开文件描述符指针
/// @param tag 自定义命名
/// @return 成功:0 失败: -1=>参数错误 -2=>达到上限
int log_add_fp(FILE *fp, const char *tag)
{
    if (fp == NULL || tag == NULL)
        return -1;
    size_t len = strlen(tag);
    len = len > LOG_TAG_LEN ? LOG_TAG_LEN : len;
    for (int i = 0; i < LOG_CB_FUNC_ARR_LEN; i++)
    {
        if (LOG.cb_func_arr[i].cb_func == NULL)
        {
            LOG.cb_func_arr[i] = (log_callback_t){
                .cb_func = fp_cb,
                .fp = fp,
                .lowlevel = 0,
            };
            strncpy(LOG.cb_func_arr[i].tag, tag, len);
            return 0;
        }
    }
    return -2;
}

/// @brief 添加文件描述符指针到全局中,并且命名指定tag字符串
/// @param fp 文件描述符指针 建议使用 ab+ 或 w 打开文件描述符指针
/// @param tag 自定义命名
/// @param lowlevel 日志最低等级
/// @return 成功:0 失败: -1=>参数错误 -2=>达到上限
int log_add_fp2(FILE *fp, const char *tag, int lowlevel)
{
    if (fp == NULL || tag == NULL)
        return -1;
    size_t len = strlen(tag);
    len = len > LOG_TAG_LEN ? LOG_TAG_LEN : len;
    for (int i = 0; i < LOG_CB_FUNC_ARR_LEN; i++)
    {
        if (LOG.cb_func_arr[i].cb_func == NULL)
        {
            LOG.cb_func_arr[i] = (log_callback_t){
                .cb_func = fp_cb,
                .fp = fp,
                .lowlevel = lowlevel,
            };
            strncpy(LOG.cb_func_arr[i].tag, tag, len);
            return 0;
        }
    }
    return -2;
}

/// @brief 更新指定tag的文件描述符
/// @param fp
/// @param tag 已存在自定义命名
/// @return >=0:更新成功的数量  -1: 参数错误
int log_upt_fp(FILE *fp, const char *tag)
{
    int ret = -1;
    if (fp == NULL || tag == NULL)
        goto fail0;
    ret = 0;
    size_t len = strlen(tag);
    len = len > LOG_TAG_LEN ? LOG_TAG_LEN : len;
    for (int i = 0; i < LOG_CB_FUNC_ARR_LEN; i++)
    {
        if (LOG.cb_func_arr[i].cb_func != NULL)
        {
            if (strncmp(LOG.cb_func_arr[i].tag, tag, len) == 0)
            {
                LOG.cb_func_arr[i].fp = fp;
                ret++;
            }
        }
    }
fail0:
    return ret;
}

/// @brief 更新指定tag的文件描述符
/// @param fp
/// @param tag 已存在自定义命名
/// @param lowlevel 最低等级
/// @return >=0:更新成功的数量  -1: 参数错误
int log_upt_fp2(FILE *fp, const char *tag, int lowlevel)
{
    int ret = -1;
    if (fp == NULL || tag == NULL)
        goto fail0;
    ret = 0;
    size_t len = strlen(tag);
    len = len > LOG_TAG_LEN ? LOG_TAG_LEN : len;
    for (int i = 0; i < LOG_CB_FUNC_ARR_LEN; i++)
    {
        if (LOG.cb_func_arr[i].cb_func != NULL)
        {
            if (strncmp(LOG.cb_func_arr[i].tag, tag, len) == 0)
            {
                LOG.cb_func_arr[i].fp = fp;
                LOG.cb_func_arr[i].lowlevel = lowlevel;
                ret++;
            }
        }
    }
fail0:
    return ret;
}

/// @brief 初始化用户日志数据的时间
/// @param event 用户日志变量 地址
/// @param fp 文件描述符指针
/// @param tag 日志文件别名
static void log_event_init(log_event_t *event, FILE *fp, const char *tag)
{
    if (event->time == NULL)
    {
        time_t t = time(NULL);
        event->time = localtime(&t);
    }
    event->fp = fp;
    // 无需判断长度了，这里直接用，外面已经判断好长度了
    if (tag != NULL)
        strcpy(event->tag, tag);
}
/// @brief 标准输出日志
/// @param level 日志等级
/// @param file __FILE__ 宏
/// @param line __LINE__ 宏
/// @param fmt 日志格式类似printf函数用法
/// @param
void log_log(int level, const char *file, int line, const char *fmt, ...)
{
    log_event_t event = {
        .level = level,
        .file = file,
        .line = line,
        .fmt = fmt,
    };

    log_event_init(&event, stdout, NULL);
    va_start(event.args, fmt);
    stdout_cb(&event);
    va_end(event.args);
}

/// @brief 文件输出日志
/// @param tag 自定义日志命名
/// @param level 日志等级
/// @param file __FILE__ 宏
/// @param line __LINE__ 宏
/// @param fmt 日志格式类似printf函数用法
/// @param
/// @return 写入成功:0 失败(没有这个tag):-1
int log_flog(const char *tag, int level, const char *file, int line, const char *fmt, ...)
{
    log_event_t event = {
        .level = level,
        .file = file,
        .line = line,
        .fmt = fmt,
    };
    int ret = -1;
    if (tag == NULL)
        goto fail0;
    for (int i = 0; i < LOG_CB_FUNC_ARR_LEN; i++)
    {
        log_callback_t *cb = &LOG.cb_func_arr[i];
        if (cb != NULL && cb->fp != NULL && cb->cb_func != NULL && strcmp(cb->tag, tag) == 0 && level >= cb->lowlevel)
        {
            if (LOG.lock_func != NULL)
                LOG.lock_func(LOG_FILE_LOCK, cb->fp);

            log_event_init(&event, cb->fp, cb->tag);
            va_start(event.args, fmt);
            cb->cb_func(&event);
            va_end(event.args);

            ret = 0;

            if (LOG.lock_func != NULL)
                LOG.lock_func(LOG_FILE_UNLOCK, cb->fp);
        }
    }
fail0:
    return ret;
}

/// @brief 标准输出+文件输出日志
/// @param tag 自定义日志命名
/// @param level 日志等级
/// @param file __FILE__ 宏
/// @param line __LINE__ 宏
/// @param fmt 日志格式类似printf函数用法
/// @param
/// @return 写入成功:0 失败(没有这个tag):-1
int log2_log(const char *tag, int level, const char *file, int line, const char *fmt, ...)
{
    log_event_t event = {
        .level = level,
        .file = file,
        .line = line,
        .fmt = fmt,
    };
    // 标准输出
    log_event_init(&event, stdout, NULL);
    va_start(event.args, fmt);
    stdout_cb(&event);
    va_end(event.args);

    // 文件输出
    int ret = -1;
    if (tag == NULL)
        goto fail0;
    for (int i = 0; i < LOG_CB_FUNC_ARR_LEN; i++)
    {
        log_callback_t *cb = &LOG.cb_func_arr[i];
        if (cb != NULL && cb->fp != NULL && cb->cb_func != NULL && strcmp(cb->tag, tag) == 0 && level >= cb->lowlevel)
        {
            if (LOG.lock_func != NULL)
                LOG.lock_func(LOG_FILE_LOCK, cb->fp);

            log_event_init(&event, cb->fp, cb->tag);
            va_start(event.args, fmt);
            cb->cb_func(&event);
            va_end(event.args);

            ret = 0;

            if (LOG.lock_func != NULL)
                LOG.lock_func(LOG_FILE_UNLOCK, cb->fp);
        }
    }
fail0:
    return ret;
}